Web上でPiping Server経由のエンドツーエンド暗号化したファイル転送
デモ動画
https://youtu.be/yIJXh_iE-sM
データをチャンクにして複数のHTTPリクエストにして、Piping Serverで転送する仕組み。
なぜチャンクで送るか?
チャンクする最大の理由は、
エンドツーエンドで暗号化して大きいデータを送信したいのが最大の理由かもしれない。
暗号化に限らず時間的空間的に効率の良いストリームを作りたい。
ストリーミングされるため、1TBなど巨大なデータも送信できるはず。
アプリケーション
GitHubリポジトリ
https://gh-card.dev/repos/nwtgck/piping-chunk-web.svg https://github.com/nwtgck/piping-chunk-web
チャンク転送の仕様
送信者は、/mydata/1, /mydata/2, ... /mydata/n に一つのチャンクをPOSTする。
受信者は、これらをGETして、一つのデータとしてくっつける。
(複数のリクエストを同時に出したり、ストリーミングしてダウンロードしているのは、「こだわり」のところで詳しく)
データの終了は/mydata/nのContent-Length: 0でHTTP Bodyの部分がないことで表している。ヌル文字と同じアイデア。
/mydata/metaみたいな仕様を用意して、
保存してほしいファイル名
MIME Type
全体でのバイトの長さ
暗号化や圧縮するときには、生Fileからとれる長さを送れば良い
を送ったりして、進捗を「%」で表せるようにするなども良い。
エンドツーエンド暗号化
こちらを使ってReadableStreamを暗号化しながら送信している。
エンドツーエンドで暗号化する意義は、安心してデータを転送するため。サーバーが信頼できなくても安心できる。
制作のこだわり
複数のHTTPリクエストで同時に送受信
高速化が目的
Promiseの実行数を制限して複数のチャンクを複数のHTTPリクエストとして同時に送る
セマフォで制御されるように、例えば「4つに制限」すると4つのうち1つでも終了すれば次のPromiseの実行が開始される
PromiseLimiterというクラスがそのコアのロジックになっている
awaitを使ってリソースを使い果たしたりしないように設計してる
長い配列やReaderをループでread()するときに、次の要素やread()をawaitで止めるようになっている。
Promise制限するライブラリはあったが、リソースを喰う実装になっているものだったため、今回は作成した
他のプロジェクトでも使えるのでライブラリ化したい
ReadableStreamを送信できる
生ファイル以外にも、暗号化、圧縮、などのストリームに変形できる
ストリームは時間的かつ空間的にも効率が良い
複数を同時に受信し、ストリーミングしながらダウンロードできる
PromiseLimiterでPromiseを同時に動かす数を制限している
PromiseSequentialContextというクラスが、複数のPromiseの結果を順番にReadableStreamのcontroller.enqueue()するために使っている
これもライブラリとして共通化したい
ダウンロードすべきデータがReadableStreamになる
ブラウザに標準の機能としてReadableStreamをファイルとしてダウンロードする機能が現在のところないぽい
StreamSaverの実装とても気になるので、調べてみたい(何をしているかセキュリティ的にも気になる)